/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.soa.util.wx;

import com.lianzt.commondata.AbstractCommonData;
import com.lianzt.commondata.DataConvertFactory;
import com.lianzt.exception.InstanceDataException;
import com.lianzt.util.HttpConnect;
import com.lianzt.util.StringUtil;
import com.soa.exception.GlobalException;
import com.soa.redis.RedisTemplate;
import com.soa.util.SystemUtil;
//import it.sauronsoftware.jave.AudioAttributes;
//import it.sauronsoftware.jave.Encoder;
//import it.sauronsoftware.jave.EncodingAttributes;
//import it.sauronsoftware.jave.FFMPEGLocator;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * 微信相关工具
 *
 * @author zuotai
 */
@Component
public class WeixinUtil {

    @Resource
    private RedisTemplate redisTemplate;
    private final Logger log = LoggerFactory.getLogger(WeixinUtil.class);
    private final String WX_ACCESS_TOKEN_KEY = "kslp_wx_access_token";
    private final String WX_JSAPI_TICKET_KEY = "kslp_wx_jsapi_ticket";
    private final String base = "abcdefghijklmnopqrstuvwxyz0123456789";     //随机字符串生成器
    /**
     * 超时时间
     */
    protected final int TIMEOUT = 10000;
    /**
     * 用于生成微信临时二维码scene_idi值的序列
     */
    private final String WX_SCENE_ID_SEQ = "wx_scene_id_seq";
    /**
     * 微信临时二维码生成的请求json
     */
    private final String WX_SCENE_REQ_JSON = "{\"expire_seconds\": %d, \"action_name\": \"QR_SCENE\", \"action_info\": {\"scene\": {\"scene_id\": %d}}}";
    /**
     * 二维码生成接口
     */
    public final String QRCODE = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=";
    /**
     * 模板短信
     */
    public final String TMPLATE_MSG = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=";
    /**
     * 获取 jsapi_ticket
     */
    public final String JSAPI_TICKET = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=";
    /**
     * 获取用户增减数据：getusersummary
     */
    public final String GET_USER_SUMMARY = "https://api.weixin.qq.com/datacube/getusersummary?access_token=";
    /**
     * 获取累计用户数据：getusercumulate
     */
    public final String GET_USER_CUMULATE = "https://api.weixin.qq.com/datacube/getusercumulate?access_token=";
    /**
     * 下载多媒体信息
     */
    public final String DOWN_FILE = "http://file.api.weixin.qq.com/cgi-bin/media/get?access_token={}&media_id=";
    /**
     * 微信临时带参二维码在内存库中的前缀
     */
    public final String PRE_SCENE_KEY = "wx_scene_id_";
    /**
     * 微信临时带参二维码，对应内存库中事件的类型：绑定业务员
     */
    public final int EVENT_BIND_YG = 1;
    /**
     * 微信文件类型:音频
     */
    public final String MEDIA_VOICE = "voice";
    /**
     * 微信文件类型:图片
     */
    public final String MEDIA_IMAGE = "image";
    /**
     * 微信文件类型:缩略图
     */
    public final String MEDIA_THUMB = "thumb";
    private final Map<String, String> MEDIA_FIX = new HashMap<String, String>();

    /**
     * 初始化
     */
    @PostConstruct
    private void init() {
        MEDIA_FIX.put(MEDIA_IMAGE, "jpg");
        MEDIA_FIX.put(MEDIA_THUMB, "jpg");
        MEDIA_FIX.put(MEDIA_VOICE, "mp3");
    }

    /**
     * 获取微信access token
     *
     * @return
     */
    public String getAccessToken() {
        String token = redisTemplate.get(WX_ACCESS_TOKEN_KEY);
        if (StringUtil.isNull(token)) {
            String appId = SystemUtil.getSysConfig("wx_appid");
            String appSecret = SystemUtil.getSysConfig("wx_appsecret");
            StringBuilder backJson = new StringBuilder();
            String urlStr = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + appSecret;
            URLConnection conn = null;
            InputStream is = null;
            try {
                log.info("获取access token url:{}", urlStr);
                URL url = new URL(urlStr);
                conn = url.openConnection();
                conn.setReadTimeout(12 * 1000);
                conn.connect();
                is = conn.getInputStream();
                int i = 0;
                byte[] bt = new byte[1024 * 10];

                while ((i = is.read(bt)) != -1) {
                    backJson.append(new String(bt, "utf-8"));
                }
                AbstractCommonData acd = DataConvertFactory.getInstanceByJson(backJson.toString());
                token = acd.getStringValue("access_token");
                if (StringUtil.isNull(token)) {
                    log.warn("获取access token返回错误码：" + acd.getStringValue("errcode") + ",内容：" + acd.getStringValue("errmsg"));
                } else {
                    redisTemplate.set(WX_ACCESS_TOKEN_KEY, acd.getIntValue("expires_in"), token);
                }
            } catch (IOException ex) {
                log.error("获取微信access token失败", ex);
            } catch (InstanceDataException ex) {
                log.error("构造access token的CommonData错误 ->" + backJson, ex);
            } finally {
                try {
                    is.close();
                } catch (Exception ex) {
                }
            }
        }
        log.info("返回token-->" + token);
        return token;
    }

    /**
     * 调用接口
     *
     * @param api
     * @param json
     * @return
     */
    public AbstractCommonData callApi(String api, AbstractCommonData json) {
        return callApi(api, DataConvertFactory.praseNormJson(json));
    }

    /**
     * 调用接口
     *
     * @param api
     * @param json
     * @return
     */
    public AbstractCommonData callApi(String api, String json) {
        if (StringUtil.isNull(json)) {
            return null;
        }
        URL url = null;
        URLConnection con = null;
        BufferedWriter bw = null;
        BufferedReader br = null;

        try {
            url = new URL(api + getAccessToken());
            con = url.openConnection();
            /**
             * 然后把连接设为输出模式。URLConnection通常作为输入来使用，比如下载一个Web页。
             * 通过把URLConnection设为输出，你可以把数据向你个Web页传送。下面是如何做：
             */
            con.setDoOutput(true);
            con.setConnectTimeout(TIMEOUT);
            con.setReadTimeout(TIMEOUT);
            con.addRequestProperty("Content-Type", "text/plain");
            /**
             * 最后，为了得到OutputStream，简单起见，把它约束在Writer并且放入POST信息中，例如： ...
             */
            bw = new BufferedWriter(new OutputStreamWriter(con.getOutputStream(), "UTF-8"));
            StringBuilder sb = new StringBuilder();
            sb.append(json);
            log.info("weixin api req : {}", json);
            bw.write(sb.toString());

            bw.flush();

            String tmpStr;
            sb = new StringBuilder();

            br = new BufferedReader(new InputStreamReader(con.getInputStream(), "utf-8"));
            while ((tmpStr = br.readLine()) != null) {
                sb.append(tmpStr);
            }
            log.info("weixin api res : {}", sb);
            AbstractCommonData res = DataConvertFactory.getInstanceByJson(sb.toString().trim());
            if (res.containsKey("errcode") && res.getIntValue("errcode") != 0) {
                //当errcode=40001时，可重新获取access token
                //异常
                throw new GlobalException(300004, res.getStringValue("errcode") + "-" + res.getStringValue("errmsg"));          //请求处理错误:!#!
            }
            return res;
        } catch (SocketTimeoutException e) {
            //超时
            throw new GlobalException(300001, e);      //请求wx-server超时
        } catch (IOException e) {
            //网络异常
            log.debug("网络异常:", e);
            throw new GlobalException(300002, e);      //wx-server连接异常
        } catch (InstanceDataException e) {
            //响应格式异常
            throw new GlobalException(300003, e);      //wx-server响应格式错误
        } finally {
            if (bw != null) {
                try {
                    bw.close();
                } catch (Exception e) {
                    log.warn("关闭连接异常：" + e.toString());
                }
            }
            if (br != null) {
                try {
                    br.close();
                } catch (Exception e) {
                    log.warn("关闭连接异常：" + e.toString());
                }
            }
        }
    }

    /**
     * 生成临时二维码
     *
     * @param expire 有效时间
     * @param sceneId 参数
     * @return 微信返回
     */
    public AbstractCommonData getQRsence(int expire, int sceneId) {
        String json = String.format(WX_SCENE_REQ_JSON, expire, sceneId);
        return callApi(QRCODE, json);
    }

    /**
     * 获取jsapi_ticket
     *
     * @return 出现异常时返回null
     */
    public String getJsapiTicket() {
        //先从内存库中取
        String ticket = redisTemplate.get(WX_JSAPI_TICKET_KEY);
        if (StringUtil.isNull(ticket)) {
            //如果取出的值为空,需要从微信服务端获取
            HttpConnect hc = null;
            try {
                hc = new HttpConnect(JSAPI_TICKET + getAccessToken(), "utf-8", TIMEOUT);
                String json = hc.receMsg();
                log.info("获取微信jsapi_ticket响应:{}", json);
                if (StringUtil.isNull(json)) {
                    log.warn("获取微信jsapi_ticket响应数据为空:");
                    return null;
                }
                AbstractCommonData res = DataConvertFactory.getInstanceByJson(json.trim());
                if (res.containsKey("errcode") && res.getIntValue("errcode") == 0) {
                    //收到响应后把jsapi_ticket放入redis
                    ticket = res.getStringValue("ticket");
                    redisTemplate.set(WX_JSAPI_TICKET_KEY, res.getIntValue("expires_in"), ticket);
                    return ticket;
                }
                log.warn("获取微信jsapi_ticket错误:{}", res.getStringValue("errmsg"));
                return null;
            } catch (InstanceDataException e) {
                log.warn("获取微信jsapi_ticket响应数据格式不为json:", e);
                return null;
            } catch (SocketTimeoutException e) {
                log.warn("获取微信jsapi_ticket超时:", e);
                return null;
            } catch (IOException e) {
                log.warn("获取微信jsapi_ticket网络异常:", e);
                return null;
            } finally {
                if (hc != null) {
                    hc.closeStream();
                }
            }
        }
        return ticket;
    }

    /**
     * 获取jsapi的签名包
     *
     * @param url /kslp/wx/ 之后的内容
     * @return
     */
    public AbstractCommonData getJsapiSign(String url) {
        String signUrl = "http://" + SystemUtil.getSysConfig("wx_js_sdk_host") + "/trffmgr-wx/wx/" + url;
        String ticket = getJsapiTicket();
        String noncestr = getRandomStringByLength(32);
        String timestamp = Long.toString(new Date().getTime() / 1000);
        String str = "jsapi_ticket=" + ticket + "&noncestr=" + noncestr + "&timestamp=" + timestamp + "&url=" + signUrl;
        log.debug("待签名字符串:{}", str);
        String signature = new SHA1().getDigestOfString(str.getBytes());
        AbstractCommonData acd = DataConvertFactory.getInstanceEmpty();
        acd.putBooleanValue("debug", false);
        acd.putStringValue("appId", SystemUtil.getSysConfig("wx_appid"));
        acd.putStringValue("timestamp", timestamp);
        acd.putStringValue("nonceStr", noncestr);
        acd.putStringValue("signature", signature);
        log.debug("响应签名包:{}", acd);
        return acd;
    }

    /**
     * 获取一定长度的随机字符串
     *
     * @param length 指定字符串长度
     * @return 一定长度的字符串
     */
    public String getRandomStringByLength(int length) {
        Random random = new Random();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            sb.append(base.charAt(random.nextInt(base.length())));
        }
        return sb.toString();
    }

    /**
     * 获取一个scene_id
     *
     * @return
     */
    public int getSceneId() {
        return redisTemplate.incr(WX_SCENE_ID_SEQ).intValue();
    }

    /**
     * 微信xml转data
     *
     * @param xml
     * @return
     */
    public AbstractCommonData xml2Data(String xml) throws DocumentException {
        Document document = new SAXReader().read(new StringReader(xml));
        Element root = document.getRootElement();
        if ("xml".equals(root.getName())) {
            Iterator<Element> iterator = root.elementIterator();
            AbstractCommonData acd = DataConvertFactory.getInstanceEmpty();
            while (iterator.hasNext()) {
                Element item = iterator.next();
                acd.putStringValue(item.getName(), item.getText());
            }
            return acd;
        } else {
            log.error("解析非法的微信xml");
            return null;
        }
    }

    /**
     * 下载多媒体文件,不同类型文件对应不同的目录,由nginx发布
     * @param type 文件类型:image, voice, video, thumb
     * @param mediaId
     */
//    public void downFile(String type, String mediaId) {
//        FileConnect fc = null;
//        try {
//            fc = new FileConnect(DOWN_FILE.replace("{}", getAccessToken()) + mediaId);
//            if (MEDIA_VOICE.equals(type)) {       //如果是语音格式的，转为mp3
//                final String path = SystemUtil.getSysConfig("media_dir") + File.separator + type;
//                String fileName = mediaId + "." + MEDIA_FIX.get(type);
//                fc.downFile(path, mediaId);     //保存时不加扩展名
//                File sourceFile = new File(path, mediaId);
//                File targetFile = new File(path, fileName);
//
//                EncodingAttributes attr = new EncodingAttributes();
//                AudioAttributes audio = new AudioAttributes();
//                attr.setAudioAttributes(audio);
//                attr.setFormat("mp3");
//                Encoder encoder = new Encoder();
//                try {
//                    encoder.encode(sourceFile, new File(path, fileName), attr);
//                } catch (Exception e) {
//                    //此处一直报异常，但是mp3可以正常转换出来，所以用debug输出，不使用warn
//                    log.debug("media_id:{}，格式转换异常：", mediaId, e);
//                }
//                if (targetFile.isFile()) {
//                    sourceFile.deleteOnExit();
//                }
//            } else {
//                fc.downFile(SystemUtil.getSysConfig("media_dir") + File.separator + type, mediaId + "." + MEDIA_FIX.get(type));
//            }
//        } catch (IOException e) {
//            log.debug("文件下载失败:", e);
//        }
//    }

    public static void main(String[] args) {
    }
}
